#include <chrono>  
#include "Log/SystemLog.h"
#include "VideoSoftDecoder.h"

// Construct the VideoSoftDecoder
VideoSoftDecoder::VideoSoftDecoder() :
	m_pDecCodecCtx(NULL),
	m_pParserCtx(NULL),
	m_bIsHaveInitDecoder(false),
	m_bDisposed(false)
{
	Initialize();
}

// Detructe the VideoSoftDecoder
VideoSoftDecoder::~VideoSoftDecoder()
{
	Destory();
}

// Initialize the decoder
void VideoSoftDecoder::Initialize()
{

}

// Destory the decoder
void VideoSoftDecoder::Destory()
{
	if (!GetDisposed())
	{
		SetDisposed(true);

		// Release the decoder
		ReleaseDecoder();
	}
}

// Release the decoder
void VideoSoftDecoder::ReleaseDecoder()
{
	AVCodecContext* pAvctx = GetDecCodecCtx();

	if (pAvctx)
	{
		avcodec_close(pAvctx);

		avcodec_free_context(&pAvctx);

		av_free(pAvctx);

		SetDecCodecCtx(NULL);
	}

	AVCodecParserContext* pParserCtx = GetParserCtx();

	if (pParserCtx)
	{
		av_parser_close(pParserCtx);

		SetParserCtx(NULL);
	}
}

// Flush the decode at the last frame decoding
void VideoSoftDecoder::Flush()
{
	AVFrame* pFrame = av_frame_alloc();

	Decode(GetDecCodecCtx(), NULL, pFrame);

	av_frame_free(&pFrame);
}

// Init the decoder
bool VideoSoftDecoder::Init(VideoTypeEnum eVideoType)
{
	AVCodec* pDecoder = NULL;

	AVCodecID eCodeId = AV_CODEC_ID_HEVC;

	if (eVideoType == H264_VIDEO)
	{
		eCodeId = AV_CODEC_ID_H264;
	}
	else if (eVideoType == H265_VIDEO)
	{
		eCodeId = AV_CODEC_ID_HEVC;
	}

	pDecoder = avcodec_find_decoder(eCodeId);

	if (pDecoder == NULL)
	{
		std::string strErrorMsg = std::string("Failed to find ")
			+ av_get_media_type_string(AVMEDIA_TYPE_VIDEO)
			+ " codec\n";

		LOG_ERROR_EX(strErrorMsg);

		return false;
	}

	// Create decode context
	AVCodecContext* pAvctx = avcodec_alloc_context3(pDecoder);

	pAvctx->flags |= AV_CODEC_FLAG_LOW_DELAY;

	pAvctx->thread_count = 0;

	SetDecCodecCtx(pAvctx);

	// Open the decode context
	if (avcodec_open2(pAvctx, pDecoder, NULL) < 0)
	{
		std::string strErrorMsg = std::string("Failed to open ")
			+ av_get_media_type_string(AVMEDIA_TYPE_VIDEO)
			+ " codec\n";

		LOG_ERROR_EX(strErrorMsg);

		ReleaseDecoder();

		return false;
	}

	// Init the parser context
	AVCodecParserContext* pParserCtx = av_parser_init(eCodeId);

	if (pParserCtx == NULL)
	{
		LOG_ERROR_EX("Failed to init the parser context!");

		ReleaseDecoder();

		return false;
	}

	SetParserCtx(pParserCtx);

	return true;
}

// Decode the packet
void VideoSoftDecoder::Decode(AVCodecContext* pAvctx, AVPacket* pPacket, AVFrame* pFrame)
{
	if (pAvctx == NULL)
	{
		LOG_ERROR_EX("Decoder context is null !");

		return;
	}

	if (pFrame == NULL)
	{
		LOG_ERROR_EX("You must alloc frame at first !");

		return;
	}

	int iRet = avcodec_send_packet(pAvctx, pPacket);

	if (iRet < 0)
	{
		LOG_ERROR_EX("Failed to send packet to decoder to decode !");

		return;
	}

	iRet = avcodec_receive_frame(pAvctx, pFrame);

	// Not ready for whole frame output so that we quit here to get another packet
	if (iRet == AVERROR(EAGAIN) || iRet == AVERROR_EOF)
	{
		return;
	}
	else if (iRet < 0)
	{
		LOG_ERROR_EX("Error happed when in decoding!");

		return;
	}
}

// Excute the decoding
bool VideoSoftDecoder::Excute(uint8_t* pSrcData,
	int iSrcSize,
	uint8_t* pDst,
	int& iDstSize,
	int& iFrameWidth,
	int& iFrameHeight)
{
	if (pSrcData == NULL)
	{
		LOG_ERROR_EX("Source data is null !");

		return false;
	}

	if (iSrcSize <= 0)
	{
		LOG_ERROR_EX("Source data size <= 0 !");

		return false;
	}

	if (pDst == NULL)
	{
		LOG_ERROR_EX("Dest buffer is null,you must alloc dest buffer at first !");

		return false;
	}

	AVCodecContext* pAvctx = GetDecCodecCtx();

	if (pAvctx == NULL)
	{
		LOG_ERROR_EX("You must init the decoder at first !");

		return false;
	}

	AVCodecParserContext* pParserCtx = GetParserCtx();

	if (pParserCtx == NULL)
	{
		LOG_ERROR_EX("You must init the parser context at first !");

		return false;
	}

	AVPacket* pPacket = av_packet_alloc();
	
	AVFrame* pFinalFrame = av_frame_alloc();

	auto DestoryPacket = [](AVPacket*& pPacket) {
	
		if (pPacket)
		{
			av_packet_unref(pPacket);

			pPacket = NULL;
		}
	};

	auto DestoryFrame = [](AVFrame*& pFrame) {

		if (pFrame)
		{
			av_frame_free(&pFrame);

			pFrame = NULL;
		}
	};

	while (iSrcSize > 0)
	{
		int iLen = av_parser_parse2(pParserCtx,
			pAvctx,
			&pPacket->data,
			&pPacket->size,
			pSrcData,
			iSrcSize,
			AV_NOPTS_VALUE,
			AV_NOPTS_VALUE,
			0);

		if (iLen < 0)
		{
			LOG_ERROR_EX("Failed to parse the packet by av_parser_parse2");

			DestoryPacket(pPacket);

			DestoryFrame(pFinalFrame);

			return false;
		}

		pSrcData += iLen;

		iSrcSize -= iLen;

		// Decode the packet
		if (pPacket->size > 0)
		{
			Decode(pAvctx, pPacket, pFinalFrame);

			av_packet_unref(pPacket);
		}
	}

	if (pFinalFrame->data == NULL)
	{
		LOG_DEBUG_EX("Not ready for a whole frame output!");

		DestoryPacket(pPacket);

		DestoryFrame(pFinalFrame);

		return false;
	}

	// Get the frame size
	int iFrameSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
		pFinalFrame->width,
		pFinalFrame->height,
		AV_PIX_FMT_YUV420P);

	iDstSize = iFrameSize;

	// Copy the frame to dest
	int iRet = av_image_copy_to_buffer(pDst,
		iDstSize,
		(const uint8_t * const*)pFinalFrame->data,
		(const int*)pFinalFrame->linesize,
		AV_PIX_FMT_YUV420P,
		pFinalFrame->width,
		pFinalFrame->height,
		AV_PIX_FMT_YUV420P);

	if (iRet < 0)
	{
		LOG_ERROR_EX("Can not copy image to buffer at the last");

		DestoryPacket(pPacket);

		DestoryFrame(pFinalFrame);

		return false;
	}

	DestoryPacket(pPacket);

	DestoryFrame(pFinalFrame);

	return true;
}
